package de.bmuskalla.brainfuck;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class BFInterpreter {

	private int[] cells;
	private int pointer;
	private String program;
	private char[] output;
	private int lastOutput;
	private Stack jumpPositions;
	private List breakpoints = new ArrayList();
	private int currentLine = 1;
	private boolean debugMode;
	private boolean suspend;
	private String[] stack = new String[100];
	private PrintStream outputStream;

	public void load(String string) {
		resetState();
		program = string;
	}

	private void execute() throws InterruptedException {
		// suspend here to wait for deferred breakpoints
		if (debugMode) {
			sendEvent("started");
			suspend = true;
			while (suspend) {
				Thread.sleep(1000);
			}
			sendEvent("resumed");
		}
		char[] charArray = program.toCharArray();
		for (int i = 0; i < charArray.length; i++) {
			char c = charArray[i];
			if (c == '.' || c == ',' || c == '[' || c == ']' || c == '<'
					|| c == '>' || c == '+' || c == '-') {
				if (stack[currentLine] == null) {
					stack[currentLine] = "";
				}
				stack[currentLine] += c;
			}
			switch (c) {
			case '+':
				cells[pointer]++;
				break;
			case '-':
				cells[pointer]--;
				break;
			case '.':
				char d = (char) cells[pointer];
				output[lastOutput] = d;
				System.out.println(d);
				lastOutput++;
				break;
			case '>':
				pointer++;
				break;
			case '<':
				pointer--;
				break;
			case '[':
				if (cells[pointer] > 0) {
					jumpPositions.push(new Integer(i));
				}
				break;
			case ']':
				if (cells[pointer] > 0) {
					i = ((Integer) jumpPositions.pop()).intValue();
				}
				break;
			case '\n':
				currentLine++;
				evaluateBreakpoint();
				break;
			}
		}
		sendEvent("terminated");
	}

	private void startEventSocket() {
		System.out.println("starting event socket");
		if (debugMode) {
			try {
				System.out.println("starting event socket - really!");
				ServerSocket server = new ServerSocket(6671);
				Socket accept = server.accept();
				outputStream = new PrintStream(accept.getOutputStream());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private void sendEvent(String event) {
		if (debugMode) {
			outputStream.println(event);
		}
	}

	private void evaluateBreakpoint() throws InterruptedException {
		if (debugMode) {
			for (int i = 0; i < breakpoints.size(); i++) {
				Integer bp = (Integer) breakpoints.get(i);
				if (bp.intValue() == currentLine) {
					suspend = true;
					break;
				}
			}
			while (suspend) {
				Thread.sleep(1000);
			}
			sendEvent("resumed");
		}
	}

	private void resetState() {
		cells = new int[10];
		output = new char[100];
		pointer = 0;
		lastOutput = 0;
		jumpPositions = new Stack();
	}

	public void run(boolean debugMode) {
		resetState();
		this.debugMode = debugMode;
		startEventSocket();
		try {
			execute();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public char[] getOutput() {
		return output;
	}

	public void addBreakpoint(int line) {
		breakpoints.add(new Integer(line));
	}

	public void resume() {
		suspend = false;
	}

	public String[] getStack() {
		return stack;
	}

}
